from __future__ import annotations

"""
健康检查服务
------------
E2 阶段：健康检查与告警
"""

import datetime as dt
import logging
from typing import Dict, Optional

from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession

from ..db import SessionLocal
from ..models import Decision, DecisionStatus
from ..services.mcp_session import mcp

logger = logging.getLogger(__name__)


class HealthStatus:
    """健康状态"""
    db_ok: bool = False
    mcp_ok: bool = False
    last_tick_time: Optional[dt.datetime] = None
    last_tick_delay_seconds: Optional[float] = None
    consecutive_failures: int = 0
    bad_args_count: int = 0


async def check_db_health(session: AsyncSession) -> bool:
    """检查数据库连通性"""
    try:
        await session.execute(select(1))
        return True
    except Exception as e:
        logger.error(f"数据库健康检查失败: {e}")
        return False


async def check_mcp_health() -> bool:
    """检查 MCP 连通性"""
    try:
        await mcp.start()
        if not mcp.enabled:
            return False
        tools = await mcp.list_tools()
        return len(tools) > 0
    except Exception as e:
        logger.error(f"MCP 健康检查失败: {e}")
        return False


async def get_last_tick_time(run_id: Optional[str] = None) -> Optional[dt.datetime]:
    """获取最近一次成功的 tick 时间"""
    async with SessionLocal() as session:
        stmt = select(func.max(Decision.ts))
        if run_id:
            stmt = stmt.where(Decision.run_id == run_id)
        
        result = await session.execute(stmt)
        return result.scalar()


async def check_health(run_id: Optional[str] = None) -> HealthStatus:
    """执行完整健康检查"""
    status = HealthStatus()
    
    # 检查数据库
    async with SessionLocal() as session:
        status.db_ok = await check_db_health(session)
    
    # 检查 MCP
    status.mcp_ok = await check_mcp_health()
    
    # 获取最近 tick 时间
    status.last_tick_time = await get_last_tick_time(run_id)
    if status.last_tick_time:
        delay = (dt.datetime.now(dt.timezone.utc) - status.last_tick_time).total_seconds()
        status.last_tick_delay_seconds = delay
    
    # 检查最近失败次数
    async with SessionLocal() as session:
        stmt = select(func.count(Decision.id)).where(
            Decision.status == DecisionStatus.ERROR
        )
        if run_id:
            stmt = stmt.where(Decision.run_id == run_id)
        result = await session.execute(stmt)
        error_count = result.scalar() or 0
        
        # 检查连续失败（最近5条）
        recent_stmt = (
            select(Decision)
            .order_by(Decision.ts.desc())
            .limit(5)
        )
        if run_id:
            recent_stmt = recent_stmt.where(Decision.run_id == run_id)
        recent_result = await session.execute(recent_stmt)
        recent_decisions = recent_result.scalars().all()
        
        consecutive = 0
        for d in recent_decisions:
            if d.status == DecisionStatus.ERROR:
                consecutive += 1
            else:
                break
        status.consecutive_failures = consecutive
        
        # 检查 BAD_ARGS 数量（最近10条）
        bad_args_stmt = (
            select(func.count(Decision.id))
            .where(Decision.status == DecisionStatus.BAD_ARGS)
        )
        if run_id:
            bad_args_stmt = bad_args_stmt.where(Decision.run_id == run_id)
        bad_args_result = await session.execute(bad_args_stmt)
        status.bad_args_count = bad_args_result.scalar() or 0
    
    return status


async def should_trigger_fuse(status: HealthStatus, max_daily_loss: Optional[float] = None) -> tuple[bool, Optional[str]]:
    """判断是否应该触发熔断"""
    reasons = []
    
    # 连续3个tick失败
    if status.consecutive_failures >= 3:
        reasons.append(f"连续{status.consecutive_failures}次失败")
    
    # MCP 不可用
    if not status.mcp_ok:
        reasons.append("MCP 服务不可用")
    
    # 延迟过高（超过5分钟）
    if status.last_tick_delay_seconds and status.last_tick_delay_seconds > 300:
        reasons.append(f"最近tick延迟{status.last_tick_delay_seconds:.0f}秒")
    
    # 连续BAD_ARGS过多
    if status.bad_args_count >= 5:
        reasons.append(f"连续{status.bad_args_count}次BAD_ARGS")
    
    if reasons:
        return True, "; ".join(reasons)
    
    return False, None

